<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="bkBiQ" id="bkBiQ"><span data-lake-id="u6f759b4c" id="u6f759b4c">典型回答</span></h1>
  <p data-lake-id="ue54e8b84" id="ue54e8b84"><br></p>
  <p data-lake-id="u2b249716" id="u2b249716"><span data-lake-id="u4e9c5cf1" id="u4e9c5cf1">在MySQL中，悲观锁是需要依靠数据库提供的锁机制实现的，在InnoDB引擎中，要使用悲观锁，需要先关闭MySQL数据库的自动提交属性，然后通过</span><code data-lake-id="u59c99f97" id="u59c99f97"><span data-lake-id="u2a3f4eb0" id="u2a3f4eb0">select ... for update</span></code><span data-lake-id="u5b23a404" id="u5b23a404">来进行加锁。</span></p>
  <p data-lake-id="ubf9f5066" id="ubf9f5066"><span data-lake-id="u0b052dce" id="u0b052dce">​</span><br></p>
  <p data-lake-id="u25c7b115" id="u25c7b115"><span data-lake-id="ub79092b8" id="ub79092b8">在数据库中，悲观锁的流程如下：</span></p>
  <p data-lake-id="u1cef6cda" id="u1cef6cda"><br></p>
  <ul list="uc6f904e2">
   <li fid="u9358847f" data-lake-id="ub07e5d27" id="ub07e5d27"><span data-lake-id="ua60bff5c" id="ua60bff5c">在对记录进行修改前，先尝试为该记录加上排他锁（exclusive locking）。</span></li>
   <li fid="u9358847f" data-lake-id="u54458c70" id="u54458c70"><span data-lake-id="u48a97375" id="u48a97375">如果加锁失败，说明该记录正在被修改，那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。</span></li>
   <li fid="u9358847f" data-lake-id="u680a1d2c" id="u680a1d2c"><span data-lake-id="ua66b7eb0" id="ua66b7eb0">如果成功加锁，那么就可以对记录做修改，事务完成后就会解锁了。</span></li>
   <li fid="u9358847f" data-lake-id="u6dc3d221" id="u6dc3d221"><span data-lake-id="u1c26d6f0" id="u1c26d6f0">其间如果有其他对该记录做修改或加排他锁的操作，都会等待我们解锁或直接抛出异常。</span></li>
  </ul>
  <p data-lake-id="u9fdfa308" id="u9fdfa308"><br></p>
  <p data-lake-id="u47326978" id="u47326978"><span data-lake-id="u34a70a57" id="u34a70a57">我们举一个简单的例子，如淘宝下单过程中扣减库存的需求说明一下如何使用悲观锁：</span></p>
  <p data-lake-id="uba26c7b4" id="uba26c7b4"><br></p>
  <pre lang="java"><code>
//0.开始事务
begin; 
//1.查询出商品信息
select quantity from items where id=1 for update;
//2.修改商品quantity为2
update items set quantity=2 where id = 1;
//3.提交事务
commit;
</code></pre>
  <p data-lake-id="u0a3cac06" id="u0a3cac06"><br></p>
  <p data-lake-id="u0a27ce85" id="u0a27ce85"><span data-lake-id="uf1f97847" id="uf1f97847">以上，在对id = 1的记录修改前，先通过for update的方式进行加锁，然后再进行修改。这就是比较典型的悲观锁策略。</span></p>
  <p data-lake-id="ub221fac6" id="ub221fac6"><br></p>
  <p data-lake-id="ue27d8550" id="ue27d8550"><span data-lake-id="u1fa7d230" id="u1fa7d230">如果以上修改库存的代码发生并发，同一时间只有一个线程可以开启事务并获得id=1的锁，其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。</span></p>
  <p data-lake-id="u1f4a79f8" id="u1f4a79f8"><br></p>
  <blockquote data-lake-id="u0d3c04f6" id="u0d3c04f6">
   <p data-lake-id="u3ec88557" id="u3ec88557"><span data-lake-id="uaa8588b5" id="uaa8588b5">上面我们提到，使用select…for update会把数据给锁住，不过我们需要注意一些锁的级别，MySQL InnoDB默认行级锁。行级锁都是基于索引的，如果一条SQL语句用不到索引是不会使用行级锁的，会使用表级锁把整张表锁住，这点需要注意。</span></p>
  </blockquote>
  <p data-lake-id="u1b9a410d" id="u1b9a410d"><span data-lake-id="ua1748949" id="ua1748949">​</span><br></p>
  <p data-lake-id="u4dbbd9b4" id="u4dbbd9b4"><span data-lake-id="ueda139a1" id="ueda139a1">MySQL中的乐观锁主要通过CAS的机制来实现，一般通过version版本号来实现。</span></p>
  <p data-lake-id="u5ec9df62" id="u5ec9df62"><br></p>
  <p data-lake-id="ubeb53b4f" id="ubeb53b4f"><span data-lake-id="u3b356516" id="u3b356516">CAS是项乐观锁技术，当多个线程尝试使用CAS同时更新同一个变量时，只有其中一个线程能更新变量的值，而其它线程都失败，失败的线程并不会被挂起，而是被告知这次竞争中失败，并可以再次尝试。</span></p>
  <p data-lake-id="u3cf8bc1e" id="u3cf8bc1e"><br></p>
  <p data-lake-id="ucff99ce8" id="ucff99ce8"><span data-lake-id="ub587a619" id="ub587a619">比如前面的扣减库存问题，通过乐观锁可以实现如下：</span></p>
  <p data-lake-id="uc7dc26f6" id="uc7dc26f6"><br></p>
  <pre lang="java"><code>
//查询出商品信息，quantity = 3
select quantity from items where id=1
//根据商品信息生成订单
//修改商品quantity为2
update items set quantity=2 where id=1 and quantity = 3;
</code></pre>
  <p data-lake-id="u05d8daa2" id="u05d8daa2"><br></p>
  <p data-lake-id="u873d2fba" id="u873d2fba"><span data-lake-id="u60612b25" id="u60612b25">以上，我们在更新之前，先查询一下库存表中当前库存数（quantity），然后在做update的时候，以库存数作为一个修改条件。当我们提交更新的时候，判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对，如果数据库表当前库存数与第一次取出来的库存数相等，则予以更新，否则认为是过期数据。</span></p>
  <p data-lake-id="ua2a363cb" id="ua2a363cb"><br></p>
  <h1 data-lake-id="Tj9dC" id="Tj9dC"><span data-lake-id="uccfee659" id="uccfee659">扩展知识</span></h1>
  <p data-lake-id="u9e1ce920" id="u9e1ce920"><br></p>
  <h2 data-lake-id="meuwF" id="meuwF"><span data-lake-id="u06e6c0f0" id="u06e6c0f0">悲观锁</span></h2>
  <p data-lake-id="u876ab24e" id="u876ab24e"><br></p>
  <p data-lake-id="u5786a668" id="u5786a668" style="text-align: justify"><span data-lake-id="u2d603697" id="u2d603697" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">当我们要对一个数据库中的一条数据进行修改的时候，为了避免同时被其他人修改，最好的办法就是直接对该数据进行加锁以防止并发。</span></p>
  <p data-lake-id="u9991e3ac" id="u9991e3ac" style="text-align: justify"><span data-lake-id="u32a35756" id="u32a35756" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ufabef3fd" id="ufabef3fd" style="text-align: justify"><span data-lake-id="u90a91821" id="u90a91821" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">这种借助数据库锁机制在修改数据之前先锁定，再修改的方式被称之为悲观并发控制（又名“悲观锁”，Pessimistic Concurrency Control，缩写“PCC”）。</span></p>
  <p data-lake-id="uab2e2395" id="uab2e2395" style="text-align: justify"><span data-lake-id="ua8c7af71" id="ua8c7af71" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u173f21c6" id="u173f21c6" style="text-align: justify"><span data-lake-id="ufa15df8a" id="ufa15df8a" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">之所以叫做悲观锁，是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大，所以需要在修改之前先加锁。</span></p>
  <p data-lake-id="ub17e4eb4" id="ub17e4eb4" style="text-align: justify"><span data-lake-id="u0c8d2c4b" id="u0c8d2c4b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ue2778e13" id="ue2778e13" style="text-align: justify"><span data-lake-id="ua2b35653" id="ua2b35653" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">悲观并发控制实际上是“先取锁再访问”的保守策略，为数据处理的安全提供了保证。</span></p>
  <p data-lake-id="u8e0b60e7" id="u8e0b60e7" style="text-align: justify"><span data-lake-id="u4629c2a6" id="u4629c2a6" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ub5e460ce" id="ub5e460ce" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/5378072/1666424062155-44a54020-cdd0-47f6-bc85-8c467f804021.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_61%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="ufe378855" id="ufe378855" style="text-align: justify"><span data-lake-id="uee31254e" id="uee31254e" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u5fd7cf53" id="u5fd7cf53" style="text-align: justify"><span data-lake-id="u1d7148ea" id="u1d7148ea" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">但是在效率方面，处理加锁的机制会让数据库产生额外的开销，还有增加产生死锁的机会；另外，还会降低并行性，一个事务如果锁定了某行数据，其他事务就必须等待该事务处理完才可以处理那行数据。</span></p>
  <p data-lake-id="uce44757b" id="uce44757b" style="text-align: justify"><span data-lake-id="u7b40916a" id="u7b40916a" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <h2 data-lake-id="NVhY6" id="NVhY6"><span data-lake-id="u46646110" id="u46646110" style="color: rgb(85, 85, 85)">乐观锁</span></h2>
  <p data-lake-id="ueef2c757" id="ueef2c757" style="text-align: justify"><span data-lake-id="u8101f956" id="u8101f956" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">乐观锁（ Optimistic Locking ） 是相对悲观锁而言的，乐观锁假设数据一般情况下不会造成冲突，所以在数据进行提交更新的时候，才会正式对数据的冲突与否进行检测，如果发现冲突了，则让返回用户错误的信息，让用户决定如何去做。</span></p>
  <p data-lake-id="u851ea5d7" id="u851ea5d7" style="text-align: justify"><span data-lake-id="u4e21a7df" id="u4e21a7df" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u8808b14b" id="u8808b14b" style="text-align: justify"><span data-lake-id="u36a48de0" id="u36a48de0" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">相对于悲观锁，在对数据库进行处理的时候，乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。</span></p>
  <p data-lake-id="u9e4d3446" id="u9e4d3446" style="text-align: justify"><span data-lake-id="u3271cdaa" id="u3271cdaa" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ua5be8f9a" id="ua5be8f9a" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/5378072/1666424062162-11be27bb-06fd-4799-a229-1f184b8f9808.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_47%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u16a0ee05" id="u16a0ee05" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span></p>
  <p data-lake-id="u96413c70" id="u96413c70" style="text-align: justify"><span data-lake-id="uf448718c" id="uf448718c" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的，因此尽可能直接做下去，直到提交的时候才去锁定，所以不会产生任何锁和死锁。</span></p>
  <p data-lake-id="u4c47aa4a" id="u4c47aa4a" style="text-align: justify"><span data-lake-id="u04f065c3" id="u04f065c3" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <h2 data-lake-id="nLWRu" id="nLWRu"><span data-lake-id="u5d27d872" id="u5d27d872" style="color: rgb(85, 85, 85)">如何选择</span></h2>
  <p data-lake-id="u6b4aeb00" id="u6b4aeb00" style="text-align: justify"><span data-lake-id="u39dad5cf" id="u39dad5cf" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">在乐观锁与悲观锁的选择上面，主要看下两者的区别以及适用场景就可以了。</span></p>
  <p data-lake-id="ud8149dec" id="ud8149dec" style="text-align: justify"><span data-lake-id="u31eeb086" id="u31eeb086" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u91a0227c" id="u91a0227c" style="text-align: justify"><span data-lake-id="u3e85cede" id="u3e85cede" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">1、乐观锁并未真正加锁，效率高。</span><strong><span data-lake-id="u10ef920f" id="u10ef920f">适用于读操作频繁，写操作相对较少的场景。</span></strong><span data-lake-id="ucefb7c64" id="ucefb7c64" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">一旦锁的粒度掌握不好，更新失败的概率就会比较高，容易发生业务失败。 </span></p>
  <p data-lake-id="uc5f8be41" id="uc5f8be41" style="text-align: justify"><span data-lake-id="uc646a082" id="uc646a082" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">2、悲观锁依赖数据库锁，效率低。更新失败的概率比较低。</span><strong><span data-lake-id="u669ecc87" id="u669ecc87">适用于写操作较为频繁，且并发写入的概率较高的场景。</span></strong></p>
  <p data-lake-id="ub616eb60" id="ub616eb60" style="text-align: justify"><span data-lake-id="ucde955cc" id="ucde955cc" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="uc7ea4243" id="uc7ea4243"><br></p>
 </body>
</html>